/*
 *
 *  * Copyright (c) 2024 China Unicom Digital Technology Co., Ltd.
 *  * openFuyao is licensed under Mulan PSL v2.
 *  * You can use this software according to the terms and conditions of the Mulan PSL v2.
 *  * You may obtain a copy of Mulan PSL v2 at:
 *  *          http://license.coscl.org.cn/MulanPSL2
 *  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 *  * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 *  * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *  * See the Mulan PSL v2 for more details.
 *
 */

package metric

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/json"
)

func TestAddResourceLists(t *testing.T) {
	empty := corev1.ResourceList{}
	res1 := corev1.ResourceList{
		"CPU": *resource.NewQuantity(100, resource.DecimalSI),
	}
	fmt.Printf("res1: %v\n", SumResourceList(empty, res1))

	res2 := corev1.ResourceList{
		"MEM": *resource.NewQuantity(200, resource.DecimalSI),
	}
	fmt.Printf("res2: %v\n", SumResourceList(res2, empty))

	fmt.Printf("sum of res1 and res2: %v\n", SumResourceList(res1, res2))
}

func TestMarshalNodeMetricsReport(t *testing.T) {
	inputs := []NodeUsageReport{
		{
			Node:      "192.168.0.10",
			Timestamp: metav1.Date(2024, 1, 2, 12, 13, 14, 0, time.Local),
			Window:    metav1.Duration{Duration: 10 * time.Second},
			Pods: map[PodRef]corev1.ResourceList{
				NewPodRef("ns1", "pod1"): corev1.ResourceList{
					corev1.ResourceCPU:    uint64Quantity(uint64(30), resource.DecimalSI, -9),
					corev1.ResourceMemory: uint64Quantity(50, resource.BinarySI, 0),
				},
			},
		},
		{
			Node:      "192.168.0.11",
			Timestamp: metav1.Date(2025, 11, 12, 22, 23, 24, 0, time.Local),
			Window:    metav1.Duration{Duration: 20 * time.Second},
		},
	}
	reqData, err := json.Marshal(inputs)
	assert.Nil(t, err)

	outputs := make([]NodeUsageReport, 0)
	err = json.Unmarshal(reqData, &outputs)
	assert.Nil(t, err)

	assert.Equal(t, "192.168.0.10", inputs[0].Node)
	assert.Equal(t, "192.168.0.11", inputs[1].Node)
	assert.Equal(t, metav1.Date(2024, 1, 2, 12, 13, 14, 0, time.Local), inputs[0].Timestamp)
	assert.Equal(t, metav1.Date(2025, 11, 12, 22, 23, 24, 0, time.Local), inputs[1].Timestamp)
	assert.Equal(t, metav1.Duration{Duration: 10 * time.Second}, inputs[0].Window)
	assert.Equal(t, metav1.Duration{Duration: 20 * time.Second}, inputs[1].Window)
}

func TestUint64Quantity(t *testing.T) {
	tests := []struct {
		name     string
		val      uint64
		format   resource.Format
		scale    resource.Scale
		expected int64
	}{
		{
			name:     "normal value within int64 range",
			val:      1000,
			format:   resource.DecimalSI,
			scale:    0,
			expected: 1000,
		},
		{
			name:     "zero value",
			val:      0,
			format:   resource.DecimalSI,
			scale:    3, // milli scale
			expected: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := uint64Quantity(tt.val, tt.format, tt.scale)

			if result.Value() != tt.expected {
				t.Errorf("Expected value %d, got %d", tt.expected, result.Value())
			}

			if result.Format != tt.format {
				t.Errorf("Expected format %v, got %v", tt.format, result.Format)
			}
		})
	}
}

func TestSumResourceList(t *testing.T) {
	tests := []struct {
		name     string
		res1     corev1.ResourceList
		res2     corev1.ResourceList
		expected corev1.ResourceList
	}{
		{
			name:     "both nil",
			res1:     nil,
			res2:     nil,
			expected: corev1.ResourceList{},
		},
		{
			name:     "res1 nil",
			res1:     nil,
			res2:     corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
			expected: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
		},
		{
			name:     "res2 nil",
			res1:     corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("1Gi")},
			res2:     nil,
			expected: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("1Gi")},
		},
		{
			name: "no overlapping resources",
			res1: corev1.ResourceList{
				corev1.ResourceCPU: resource.MustParse("1"),
			},
			res2: corev1.ResourceList{
				corev1.ResourceMemory: resource.MustParse("1Gi"),
			},
			expected: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("1"),
				corev1.ResourceMemory: resource.MustParse("1Gi"),
			},
		},
		{
			name: "overlapping resources with addition",
			res1: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("1"),
				corev1.ResourceMemory: resource.MustParse("512Mi"),
			},
			res2: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("2"),
				corev1.ResourceMemory: resource.MustParse("1Gi"),
			},
			expected: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("3"),
				corev1.ResourceMemory: resource.MustParse("1536Mi"), // 512Mi + 1024Mi
			},
		},
		{
			name:     "empty resource lists",
			res1:     corev1.ResourceList{},
			res2:     corev1.ResourceList{},
			expected: corev1.ResourceList{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := SumResourceList(tt.res1, tt.res2)

			// 检查结果长度
			if len(result) != len(tt.expected) {
				t.Errorf("Expected %d resources, got %d", len(tt.expected), len(result))
			}

			// 检查每个资源的值
			for resourceName, expectedQuantity := range tt.expected {
				actualQuantity, exists := result[resourceName]
				if !exists {
					t.Errorf("Expected resource %s not found in result", resourceName)
					continue
				}

				if actualQuantity.Cmp(expectedQuantity) != 0 {
					t.Errorf("For resource %s: expected %s, got %s", resourceName, expectedQuantity.String(), actualQuantity.String())
				}
			}

			// 检查没有多余的资源
			for resourceName := range result {
				if _, exists := tt.expected[resourceName]; !exists {
					t.Errorf("Unexpected resource %s found in result", resourceName)
				}
			}
		})
	}
}

func TestPrintObjMeta(t *testing.T) {
	tests := []struct {
		name     string
		input    []*metav1.PartialObjectMetadata
		expected string
	}{
		{
			name:     "empty slice",
			input:    []*metav1.PartialObjectMetadata{},
			expected: "",
		},
		{
			name:     "nil slice",
			input:    nil,
			expected: "",
		},
		{
			name: "single item with namespace",
			input: []*metav1.PartialObjectMetadata{
				{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "default",
						Name:      "pod-1",
					},
				},
			},
			expected: "Namespace:default, Name:pod-1\n",
		},
		{
			name: "single item without namespace",
			input: []*metav1.PartialObjectMetadata{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "cluster-scoped-resource",
					},
				},
			},
			expected: "Namespace:, Name:cluster-scoped-resource\n",
		},
		{
			name: "multiple items",
			input: []*metav1.PartialObjectMetadata{
				{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "default",
						Name:      "pod-1",
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "kube-system",
						Name:      "pod-2",
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "cluster-role",
					},
				},
			},
			expected: "Namespace:default, Name:pod-1\nNamespace:kube-system, Name:pod-2\nNamespace:, Name:cluster-role\n",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := PrintObjMeta(tt.input)
			if result != tt.expected {
				t.Errorf("Expected:\n%q\n\nGot:\n%q", tt.expected, result)
			}
		})
	}
}

func TestPrintNamespaceNamesForContainers(t *testing.T) {
	tests := []struct {
		name     string
		input    map[PodRef]ContanersMetricsPoint
		expected string
	}{
		{
			name:     "empty map",
			input:    map[PodRef]ContanersMetricsPoint{},
			expected: "",
		},
		{
			name:     "nil map",
			input:    nil,
			expected: "",
		},
		{
			name: "single item",
			input: map[PodRef]ContanersMetricsPoint{
				NewPodRef("default", "pod-1"): {},
			},
			expected: "Namespace/Name:default/pod-1\n",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := PrintNamespaceNamesForContainers(tt.input)
			if result != tt.expected {
				t.Errorf("Expected:\n%q\nGot:\n%q", tt.expected, result)
			}
		})
	}
}

func TestPrintNamespaceNamesForPods(t *testing.T) {
	tests := []struct {
		name     string
		input    map[PodRef]MetricsPoint
		expected string
	}{
		{
			name:     "empty map",
			input:    map[PodRef]MetricsPoint{},
			expected: "",
		},
		{
			name:     "nil map",
			input:    nil,
			expected: "",
		},
		{
			name: "single item",
			input: map[PodRef]MetricsPoint{
				NewPodRef("default", "pod-1"): {},
			},
			expected: "Namespace/Name:default/pod-1\n",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := PrintNamespaceNamesForPods(tt.input)
			if result != tt.expected {
				t.Errorf("Expected:\n%q\nGot:\n%q", tt.expected, result)
			}
		})
	}
}

func TestPrintNamespaceNamesForResList(t *testing.T) {
	tests := []struct {
		name     string
		input    map[PodRef]map[string]corev1.ResourceList
		expected string
	}{
		{
			name:     "empty map",
			input:    map[PodRef]map[string]corev1.ResourceList{},
			expected: "",
		},
		{
			name:     "nil map",
			input:    nil,
			expected: "",
		},
		{
			name: "single item",
			input: map[PodRef]map[string]corev1.ResourceList{
				NewPodRef("default", "pod-1"): {
					"container-1": {
						corev1.ResourceCPU:    resource.MustParse("100m"),
						corev1.ResourceMemory: resource.MustParse("128Mi"),
					},
				},
			},
			expected: "Namespace/Name:default/pod-1\n",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := PrintNamespaceNamesForResList(tt.input)
			if result != tt.expected {
				t.Errorf("Expected:\n%q\nGot:\n%q", tt.expected, result)
			}
		})
	}
}
